【React】レンダリングに時間がかかるページを useTransition を使ってユーザーの体験を向上させる
環境
"react": "^18.3.1",
"vite": "^5.4.1",
"react-router-dom": "^6.26.2",
"tailwindcss": "^3.4.13",
useTransitionとは
useTransition
について簡単に説明します。
const [isPending, startTransition] = useTransition();
useTransition
は UI をブロックせずに、state を更新する為の React フックになります。
2 つの要素を含む配列を返し、
1 つ目の要素はisPending
で、state が更新中かどうかを示します
2 つ目の要素はstartTransition
で、state の更新を開始する関数です。
この説明だけだと??? となると思うので、
実際にサンプルを作成しながら理解していきましょう!
完成イメージ
まずはuseTransition
を使用した場合と使用しない場合の比較を見てみましょう。
サイドメニューに「Home」「重いページ」「軽いページ」の 3 つがあります。
useTransition
を使用せずに、重いページをクリックすると、レンダリングが完了するまでユーザー操作がブロックされていることが分かります。
使用した場合、ユーザー操作がブロックされず、hover エフェクトも残っていることが分かると思います!
使用しない場合
使用した場合
環境構築
上記を参考にReact + Vite + Tailwind の環境構築を行ってください
- 今回はページ遷移を行うため、
react-router-dom
も導入します。
npm install react-router-dom
実装
useTransition を使用しない場合
まずはuseTransition
を使用せずに作成していきます。
ディレクトリ構成は以下になっています。
src/
├── components/
│ └── Layout.tsx
├── pages/
│ ├── Home.tsx
│ ├── Heavy.tsx
│ └── Light.tsx
└── main.tsx
まずは今回の肝であるHeavy.tsx
(重いページ)を作成します。
ページのレンダリングに時間がかかるように、重い計算を行う関数heavyComputation
を作成し、useEffect
を使用して初回レンダリング時に実行します。
import { useEffect, useState } from "react";
/** 重い計算を行う関数 */
const heavyComputation = (count: number): string[] => {
const result: string[] = [];
for (let i = 0; i < count; i++) {
const complexValue = Math.sin(i) * Math.cos(i) * Math.tan(i);
result.push(`item-${i}: ${complexValue.toFixed(10)}`);
}
return result;
};
const HeavyPage = () => {
const [items, setItems] = useState<string[]>([]);
useEffect(() => {
setItems(heavyComputation(50000));
}, []);
return (
<div className="p-4">
<ul>
{items.map((item) => (
<li key={item} className="mb-1">
{item}
</li>
))}
</ul>
</div>
);
};
export default HeavyPage;
そのほかのページやレイアウト、ルーティング設定を作成してきます。
その他のページ
function Home() {
return <div>Home</div>;
}
export default Home;
const LightPage = () => {
return <div>LightPage</div>;
};
export default LightPage;
import { useCallback } from "react";
import { Link, Outlet, useLocation } from "react-router-dom";
const SideBarItem = [
{
id: 1,
name: "Home",
path: "/",
},
{
id: 2,
name: "重いページ",
path: "/heavy",
},
{
id: 3,
name: "軽いページ",
path: "/light",
},
];
export const Layout = () => {
const location = useLocation();
const isActiveLink = useCallback(
(itemPath: string) => {
return location.pathname === itemPath;
},
[location.pathname]
);
return (
<div className="flex h-lvh w-lvw flex-row bg-gray-50">
<div className="w-48 bg-blue-300">
{SideBarItem.map((item) => (
<Link
key={item.id}
to={item.path}
className={`block p-2 hover:bg-blue-200 ${isActiveLink(item.path) ? "bg-blue-500 text-white" : ""}`}
>
{item.name}
</Link>
))}
</div>
<div className="w-full overflow-y-auto bg-white p-8">{<Outlet />}</div>
</div>
);
};
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./index.css";
import { Layout } from "./components/Layout.tsx";
import Home from "./pages/Home.tsx";
import HeavyPage from "./pages/Heavy.tsx";
import LightPage from "./pages/Light.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Router>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/heavy" element={<HeavyPage />} />
<Route path="/light" element={<LightPage />} />
</Route>
</Routes>
</Router>
</StrictMode>
);
これでuseTransition
を使用しない場合の実装は完了です。
画面を表示して、重いページをクリックすると、ユーザー操作がブロックされることが確認できると思います。
useTransition を使用する場合
現在のコードにuseTransition
を導入していきます。
変更はとても簡単です!
+import { useEffect, useState, useTransition } from "react";
/** 重い計算を行う関数 */
const heavyComputation = (count: number): string[] => {
const result: string[] = [];
for (let i = 0; i < count; i++) {
const complexValue = Math.sin(i) * Math.cos(i) * Math.tan(i);
result.push(`item-${i}: ${complexValue.toFixed(10)}`);
}
return result;
};
const HeavyPage = () => {
+ const [isPending, startTransition] = useTransition(); // 追加
const [items, setItems] = useState<string[]>([]);
useEffect(() => { // startTransition でラップする
+ startTransition(() => {
setItems(heavyComputation(50000));
+ });
}, []);
+ // stateが更新中の場合は、ローディングを表示
+ if (isPending) {
+ return <div>loading...</div>;
+ }
return (
<div className="p-4">
<ul>
{items.map((item) => (
<li key={item} className="mb-1">
{item}
</li>
))}
</ul>
</div>
);
};
export default HeavyPage;
これでuseTransition
を使用した場合の実装は完了です。
useTransition
を使用することで、ユーザー操作がブロックされず、かつローディングを表示することができます。
まとめ
useTransition
を使用することで、簡単にユーザー体験を向上させることができます!
ぜひ、実際に使用してみてください!